查看原文
其他

深度 | 如何掌握Facebook自动人脸识别技术?这篇文章为你提供生动指南

2016-07-25 机器之心

选自 Medium

作者:Adam Geitgey

机器之心编译

参与:杜夏德、李亚洲


不知道你注意了没?Facebook 有一项自动识别你上传照片中的朋友的功能。过去你需要自己点开照片为上面的每个人添加标注,现在这些 Facebook 都能自动为你做好。它是怎么做到的呢?看完下面这篇文,你也能在自己的电脑上 Get 这个技能!


你是否注意到 Facebook 已经开发出了一项能识别你朋友照片的神奇能力?过去,你能通过点击 Facebook 照片中的人物并输入名字为朋友上标签。现在,只要你上传一张图片,Facebook 就能非常神奇的为你标注图片中的每一个人:



Facebook 能够自动为你图片中的人物打上标签,这个人你之前要曾标注过。我不确定这是有帮助的?还是诡异的?


这一技术被称为面部识别。Facebook 的算法能够在只被标注过数次之后识别出你朋友的面部。这是一项相当惊人的技术——Facebook 识别面部的准确率达到了 98%,这几乎与人类水平一样。


让我们学习一下面部识别是如何工作的!但只是识别你的朋友就太过简单了。我们把这一技术的限度增加到更具挑战性的问题:区别 Will Ferrell(知名演员)和 Chad Smith(知名摇滚歌手)!




在复杂问题上如何使用机器学习


在过去的博文中,我们曾使用机器学习解决只有一个步骤的孤立问题——估算房价、基于已有数据产生新数据、识别一张图片中是否包含特定目标。所有这些问题都能通过选择一个机器学习算法、输入数据、产生结果而解决。但面部识别却是一系列相关的问题:


  • 首先,查看一张照片,找到里面所有的脸。

  • 第二,聚焦于每一张脸,并且即使在这张脸的朝向诡异、光照不好的情况下也能理解这是同一张脸。

  • 第三,能够挑选出面部的独特特征,从而区别它与其他人——像是眼睛有多大、脸有多长等。

  • 最后,将这张脸的这些独特特征与你已经知道的所有人的脸作对比,从而决定个人的名字。


作为人类,你的大脑能自动迅速处理这些事。事实上,人类太擅长识别人脸了,以至于能在每种东西上都「看到」人脸。:




计算机就没有这种高水平的泛化能力(至少现在没有),所以我们要手把手教它们一步步该怎么做。


我们需要建立一个单独解决人脸识别每一步的流水线,由这一步的结果进入下一步。换句话说,我们将连接数个机器学习算法:




人脸识别——一步一步来


让我们依次解决这一问题。每一步,我们都将会学习一个不同的机器学习算法。我不打算详细解释每一个算法,但你将学到每一个背后的主要思想,而且你将学到如何在 Python 上使用 OpenFace 和 dlib 建立自己的面部识别系统。


第一步:找到所有的脸


第一步是面部检测。很明显,在进行面部区分之前我们需要定位照片中的每张脸。


如果过去 10 年中你使用过照相机,你可能见过的面部检测:




面部检测是照相机的一大特征。当照相机能自动挑选面部,它可以在拍照前确保聚焦到所有的脸。但我们将使用其做不同的事——发现我们想要的图像区域,从而进入下一步。


在刚刚进入21世纪那几年,当 Paul Viola 和 Michael Jones 创造出一种能在廉价相机上运行的快速面部检测方法时,面部检测就成为了主流。然而,如今已经有太多可靠的解决方案。所以,我们打算采用 2005 年创造的方法,名为 Histogram of Oriented Gradients,缩写也就是 HOG。


为了找到图像中的脸,开始我们将会把图片变成黑白的,因为我们不需要颜色数据:




然后,我们将挨个查看图片中的单像素(single pixel)。对每一单像素而言,我们想要查看直接围绕着它的像素:




我们的目标是要搞清相比于直接围绕着它的这些像素,这个像素有多暗。然后,我们想要绘制一个箭头,显示图像变暗的方向:



只查看这个像素以及围绕着它的像素,图像变暗的方向是向上。


如果你在图片中的每个单像素重复这一过程,最终每个像素都会被箭头取代。这些箭头被称为梯度,它们显示了整张图片中由亮变暗的过程。




这可能看起来像是一件做起来很随意的事,但有很好的理由解释使用梯度取代像素的原因。如果我们直接分析像素,同一个人非常暗的图片和非常亮的图片将有完全不同的像素值。但通过只考虑 亮度变化的方向,暗或亮的照片将有同样准确的表征。这解决起来问题更加的容易。


但是,保留每个单像素的梯度给了我们太多的细节。我们最终会变得只见树木不见森林。如果我们只看更高层次的亮度/暗度基础流这样会更好,我们就能看到图片的基础模式。


为了做到这一点,我们将图片分割成 16×16 的像素方块。在每个方块下,我们将计算每个主要方向中有多少个梯度点(多少指向上、多少指向下、指向右,等等)。然后,我们将用指向最多的方向箭头取代这个方块。


最终结果是我们以一种非常简单的方式,把原始图片变成了一个能抓住面部基础结构的表征:



原始图片变为了能抓住图片主要特征的 HOG 表征,不用考虑亮度。


为了在这个 HOG 图片中找到面部,我们所需要做的就是找到最类似于已知 HOG 模式(从其他面部训练数据中提取到的)的部分:




使用这一技术,我们能简单的发现任何图像中的脸:




如果你想要使用 Python 和 dlib 自己尝试这一不走,这是如何产生和查看图片 HOG 表征的代码:https://gist.github.com/ageitgey/1c1cb1c60ace321868f7410d48c228e1


第二步:姿势以及面部的突出


我们已经孤立了图片中的每张脸。但现在,我们需要处理这样一个问题:面部朝向不同方向对计算机而言看起来完全不同:




为了处理这种情况,我们将尝试打包每一个图片,以便于眼睛和嘴巴总是处于图片中的同样位置。这会使我们在下一步中更容易比较面部。


为了做到这一点,我们打算使用一个名为面部标志估算(face landmark estimation)的算法。也有很多方式能够做到这些,但我们打算使用 Vahid Kazemi 和 Josephine Sullivan 在 2014 年创造的方法。


基础的思路是我们将提出脸上存在的 68 个特殊点(被称为 landmarks):下巴的顶部、眼睛的外沿、眉毛的内沿等等。然后,我们将训练一个机器学习算法能够在任何脸上找到这些 68 个特殊点:



我们将在每张脸上定位的这 68 个标志


这是在我们的测试图像上定位的 68 个面部标志的结果:



你也能使用同样的技术部署自己的 Snapchat 实时 3d 面部过滤器!


现在,我们知道了眼睛和嘴巴的位置,我们将简单的旋转、按比例放大或缩小以及修改图像,以便于眼睛和嘴巴最好能够居中。我们不会做任何精致的 3d 扭曲( 3d warps),因为这会使得图片失真。我们只打算使用基础的图像变换技巧,像是旋转(rotation)以及按比例放大或缩小(scale),从而保留平行线(这被称为仿射变换,affine transformations):




现在,无论面部朝向如何,我们都能粗略地将眼睛和嘴巴集中到图片中的同一位置。这将使得我们的下一步更加准确。


如果你想尝试这一步,这里是寻找面部标志的代码:https://gist.github.com/ageitgey/ae340db3e493530d5e1f9c15292e5c74;这里是使用这些标志转换图像的代码:https://gist.github.com/ageitgey/82d0ea0fdb56dc93cb9b716e7ceb364b。


第三步:给脸编码


面部识别的最简单方法是直接将我们在第二步中找到的未知的的脸与我们拥有的已经被标记过的照片相比较。当我们发现一个之前标记过的脸与未知的脸非常相似,它必定是同一个人。看上去是个好主意,对吧?


但这个方法有个很大的问题。一个像 Facebook 这样的网站拥有几十亿的用户,上面贴了几万亿张照片,它不可能循环检查之前每个标记过的脸,并将它与每一张新上传的图片比较。这样需要的时间太长了。他们要能够在毫秒之间识别不同的脸,而不是以小时为单位。


我们需要的方法是从每一张脸中提取一些基础的测量数据。然后就可以用同样的方法测量未知的脸,并且找到最接近测量数据的那张已知的脸。例如,我们可以测量每个耳朵的大小、眼睛之间的距离、鼻子的长度等。如果你曾经看过一部像犯罪现场调查(CSI)那样的电视剧,你就知道我在说什么。


1.测量一张脸的最可靠方法


OK,所以我们需要从每张脸中测量些什么来建立已知脸的数据库?耳朵的大小?鼻子的长度?眼睛的颜色?或者其它的东西?


事实上,那些对人类而言来说非常明显的测量(像眼睛的颜色)对计算机来说没啥意义,因为计算机只看图像中的单个像素。研究人员已经发现最精确的方法是让计算机自己去测量它要收集的数据。深度学习要比人类更善于搞清楚面部什么部位测量起来更重要。


解决方案是训练一个深度卷积神经网络。但并不是训练这它去识别照片中的目标,而是要训练它产生每张脸的 128 个测量。


这个训练过程是同时看 3 张人脸照片:


  1. 加载一张已知人的训练面部图像

  2. 加载另一张同一人的照片

  3. 加载一张完全不同的人的照片


然后这个算法检查它当前为这三张人脸照片生成的测量数据。然后略微调整一下这个神经网络,确保#1 和#2的测量结果稍稍接近,而 #2 和 #3 的测量结果稍稍相远:




数千人的几百万张照片需要重复这个步骤几百万次,之后这个神经网络学习可靠地为每个人生成 128 个测量。对于任何一个人的十张不同的图片,它都应该给出大致相同的测量。


机器学习人士将这每张脸的 128 项测量称为一个嵌入。这个减少复杂原始数据(如图片插入计算机生成数字列表)的想法在机器学习中出现过很多(尤其是在语言翻译中)。我们使用的面部提取的方法是谷歌研究员在 2015 年发明的,但也存在很多相似的方法。


2.为人脸图像编码


训练一个卷积神经网络输出面部嵌入的方法需要大量数据和强大的计算能力。在自己的计算机上做这个有点不切实际,除非你有 8  个 NVIDIA Telsa 视频显卡或者你愿意租用亚马逊的计算机服务。


但是一旦这个网络训练好了,它就能为任何一张脸生成测量数据,即便这张脸它从来没见过!所以,这一步只需要做一次就行了。我们很幸运,有人已经在 OpenFace 上做了,并公开了一些训练好的网络,我们可以直接使用 。


所以我们只需要用他们预先训练好的网络运行我们的人脸图像,来获得每张脸的 128 个测量数据。




那么这 128 个测量数据到底是什么?其实我们也不知道。这对我们来说真的不重要。我们所关心的是,这个神经网络在看同一个人的两个不同的图片时,产生了几乎相同测量数据。


如果你想自己尝试这个步骤,OpenFace 提供了一个 lua 脚本,将生成的所有图像嵌入在一个文件夹,把它们写成 CSV 文件。这里是步骤:https://gist.github.com/ageitgey/ddbae3b209b6344a458fa41a3cf75719


第四步:从编码中找到这个人的名字


最后一步是整个过程中最简单的一步。我们所做的都是为了在已知人的数据库中找到这个人,这个人的测量数据要最接近我们的测试图像。


你可以用任何基本的机器学习分类算法来做这个事情,不需要什么花哨的深度学习技巧。我们将使用一个简单的线性  SVM 分类器 ,但其他很多分类算法也都可以拿来用。

我们要做的就是训练一个分类器,它能吸收新测试图像的测量数据,并在已知的人中分辨出哪一个是最匹配的。运行这一分类器需要数毫秒。分类的结果就是这个人的名字。

所以让我们试试我自己的系统。首先 ,我用 Will Ferrell、Chad Smith 和 Jimmy Falon 三人每人 20 张照片来训练一个分类器。




然后我在这个分类器上运行了 YouTube 上 Will Ferrell 和 Chad Smith 在 Jimmy Falon 秀上相互模仿的那个视频的每一帧 :




结果成功了! 看看这些脸的不同姿势 —— 甚至是侧脸,都被它抓到了


自己来试试


让我们回顾一下这个步骤:


  1. 用 HOG 算法编码每一张照片,创建出这张照片的一个简单图像版本。然后用这个简单的图像找到图像的一部分,这部分看起来要像一张脸的通用 HOG 编码。

  2. 通过寻找脸的主要特征,找出脸的姿势。一旦我们发现这些特征,就用它们来弯曲图像,让眼睛和嘴巴都集中在一起。

  3. 利用一个知道如何测量面部特征的神经神经网络穿过这一集中的面部图像,保留这些 128 项测量。

  4. 查看我们过去已经测量的所有人脸,看哪一张与我们要测量的面部最为接近。这就是匹配项。


既然已知道了它是怎么做出来的,这里有一个在自己电脑上使用 OpenFace 运行整个人脸识别过程的指导:


要做的准备


确保你已经安装了 Python、OpenFace 和 dlib 。你也可以在 https://cmusatyalab.github.io/openface/setup/  这个网站上手动安装,或者使用一个已经将所有东西都安装好了的预先设定的Docker 图象:


docker pull bamos/openface

docker run -p 9000:9000 -p 8000:8000 -t -i bamos/openface /bin/bashcd /root/openface


贴士:如果你正在 OSK 上使用 Doker,你能使你的 OSK/Users/folder 在一个 docker 图像中可见,像这样:


docker run -v /Users:/host/Users -p 9000:9000 -p 8000:8000 -t -i bamos/openface /bin/bash

cd /root/openface


然后你就能获取你在/host/Users/...的docker 图像里的所有 OSX 文件


ls /host/Users/


第一步


在 openface 文件中建立一个名为./training-images/的文件夹


mkdir training-images


为你想识别的每个人建立一个子文件夹。例如:


mkdir ./training-images/will-ferrell/

mkdir ./training-images/chad-smith/

mkdir ./training-images/jimmy-fallon/


第三步


将每个人的所有图像拷贝进对应的子文件夹。确保每张图像上只出现一张脸。不需要裁剪脸部周围的区域。OpenFace 会自己裁剪。


第四步


从这个 OpenFace 的根目录中运行这个 OpenFace 脚本。


首先,进行姿势检测和校准:


./util/align-dlib.py ./training-images/ align outerEyesAndNose ./aligned-images/ --size 96


这将创建一个新./aligned-images/子文件夹,带有每一个测试图像的裁剪过的并且对齐的版本。


第二,从对齐的图像中生成表征:


./batch-represent/main.lua -outDir ./generated-embeddings/ -data ./aligned-images/


运行完后,这个./aligned-images/子文件夹会包含一个带有每张图像的嵌入的  csv 文件。


第三,训练自己的面部检测模型:


./demos/classifier.py train ./generated-embeddings/


这将产生名为./generated-embeddings/classifier.pkl的新文件名。这个文件有你将用来识别新面部的 SVM 模型。


到了这,你应该有一个可用的面部识别器。


第五步:识别面部!


获取一张未知脸的新照片。把它像这样传递给分类器脚本:


./demos/classifier.py infer ./generated-embeddings/classifier.pkl your_test_image.jpg


你需要得到一个看起来像这样的预测:


=== /test-images/will-ferrel-1.jpg ===

Predict will-ferrell with 0.73 confidence.


从这里开始直到你适应这个 ./demos/classifier.py Python 脚本 做任何你想做的。

重要提示:


  • 如果你得到了坏的结果,请尝试在第三步中为每个人添加更多一些照片(尤其是不同姿势的照片)。

  • 这个脚本总是会给出一个预测,即便是一张它不知道的脸。在真实的应用中,你会看到信度得分,并抛除低信度的预测,因为它们很可能是错误的。



©本文由机器之心编译,转载请联系本公众号获得授权。

✄------------------------------------------------

加入机器之心(全职记者/实习生):hr@almosthuman.cn

投稿或寻求报道:editor@almosthuman.cn

广告&商务合作:bd@almosthuman.cn


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存